package immutablecollections.functions;

import immutablecollections.*;
import immutablecollections.misc.*;
import java.lang.reflect.*;

class SimpleInvoker<O> extends Invoker<O>
{

    final private Method method;

    final private Object lastArgArray;

    final private int lastIndex;

    public SimpleInvoker(Method invoker)
    {
        this.method = invoker;

        if (method.isVarArgs())
        {
            Class<?>[] parameterTypes = method.getParameterTypes();

            lastIndex = parameterTypes.length - 1;
            lastArgArray = Array.newInstance(parameterTypes[lastIndex].getComponentType(), 1);
        }
        else
        {
            lastIndex = 0;
            lastArgArray = null;
        }
    }

    public O invoke(ImList<Object> args)
    {
        try
        {
            return Modifier.isStatic(method.getModifiers())
                    ? (O) method.invoke(null, toArray(args))
                    : (O) method.invoke(args.head(), toArray(args.tail()));
        }
        catch (Exception e)
        {
            throw new RuntimeException("Problem accessing invoker " + method + " with arguments " + args + " on "
                    + method.getDeclaringClass() + "\nOriginal exception:\n", e);
        }
    }

    private Object[] toArray(ImList<Object> args)
    {
        Object[] arr = args.toArray();

        if (method.isVarArgs() && !arr[lastIndex].getClass().isArray())
        {
            // Weeellll - the method is a Varargs method and the last arg we are supplying to it is not an array
            // We are going to guess that the best thing to do is to wrap the last arg in an array.
            Array.set(lastArgArray, 0, arr[lastIndex]);
            arr[lastIndex] = lastArgArray;
        }

        return arr;
    }

    @Override
    public String toString()
    {
        return toString(method, (ImList<Object>) ImList.empty());
    }

    public String toString(ImList<Object> args)
    {
        return toString(method, args);
    }

    public String toString(Method method, ImList<Object> fixedArgs)
    {
        String f = TextUtils.join(fixedArgs.map(FnFactory.toStringFn).iterator(), " ");

        // Hmm - should be using something like join (zip blah repeat " ")
        String fixed = fixedArgs.isEmpty()
                ? " "
                : " " + f + " ";

        // Get the arg types for the invoker
        ImList<Class<?>> rawArgTypes = getArgClasses(method);

        // We need to remove the args that have been fixed
        ImList<Class<?>> types = rawArgTypes.drop(fixedArgs.size());

        // COnvert them to their simple names
        ImList<String> typeNames = types.map(FnFactory.getSimpleNameFn);

        return method.getDeclaringClass().getSimpleName() + "#" + method.getName() + fixed + ":: "
                + TextUtils.join(typeNames, " -> ");
    }

    private ImList<Class<?>> getArgClasses(Method method)
    {
        ImList<Class<?>> rawArgTypes = ImList.onArray(method.getParameterTypes()).addingAll(
                ImList.on(method.getReturnType()));

        if (!Modifier.isStatic(method.getModifiers()))
            return ImList.cons(method.getDeclaringClass(), rawArgTypes);
        else
            return rawArgTypes;
    }

}
